﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ProgNetComponents.Collections;
using DevExpress.XtraEditors;
using DevExpress.XtraBars;
using System.ComponentModel;
using DevExpress.XtraEditors.Controls;
using System.Windows.Forms;
using ProgNetComponents.Interfaces;
using System.Collections;

namespace ProgNetComponentsDx.Options
{
    public static class OptionCache
    {
        public static OptionGrabber Grabber { get; set; }

        public static event OptionChangedEventHandler OptionChanged;
        public static void OnOptionChanged(object optionId)
        {
            if (OptionChanged != null)
                OptionChanged(optionId);
        }
        
        private static NamedObjectCollection _RegisteredOptions = new NamedObjectCollection();
        public static NamedObjectCollection RegisteredOptions
        {
            get { return _RegisteredOptions; }
        }

        private static NamedObjectCollection _SavedOptions = new NamedObjectCollection();
        public static NamedObjectCollection SavedOptions
        {
            get { return _SavedOptions; }
        }

        public static bool IsTheSameId(object id1, object id2)
        {
            return GetKey(id1) == GetKey(id2);
        }

        #region static
        private static bool temporaryLock;
        public static bool IsOptionSaved(object optionId)
        {
            string key = GetKey(optionId);
            return SavedOptions.Contains(key);
        }
        public static object GetOptionValue(object optionId, object defVal=null)
        {
            Option o = GetOption(optionId);
            object value = null;
            if (o != null)
            {
                string key = GetKey(optionId);
                if (SavedOptions.Contains(key))
                    value = SavedOptions[key].Value;
                else
                    value = o.DefaultValue;
                try
                {
                    if (o.OptionType != null && o.OptionType.IsEnum)
                    {
                        Array vals = Enum.GetValues(o.OptionType);
                        foreach (object v in vals)
                            if (v.AsString() == value.AsString())
                                return v;
                    }
                    if (value != null)
                        value = Convert.ChangeType(value, o.OptionType);
                }
                catch { }
            }
            else
            {
                string key = GetKey(optionId);
                if (SavedOptions.Contains(key))
                {
                    value = SavedOptions[key].Value;
                    if (defVal != null)
                    {
                        try
                        {
                            value = value.ChangeType(defVal.GetType());
                        }
                        catch { }
                    }

                }
                else return defVal;
            }
            return value;
        }
        public static bool GetOptionBoolValue(object optionId)
        {
            return GetOptionValue(optionId).AsBool();
        }
        public static string GetOptionStringValue(object optionId)
        {
            return GetOptionValue(optionId).AsString();
        }
        
        public static void SetOptionValue(object optionId, object value, bool save = false, bool updateControls = false)
        {
            Grabber.SetOptionValue(optionId, value, save, updateControls);
            string key = GetKey(optionId);
            bool modified = false;
            if (SavedOptions.Contains(key))
            {
                if (!SavedOptions[key].Value.IsTheSame(value))
                {
                    SavedOptions[key].Value = value;
                    modified = true;
                }
            }
            else
            {
                SavedOptions.Add(key, value);
                modified = true;
            }
            if (save && modified)
            {
                Grabber.SaveSingleOption(optionId);
                if (updateControls)
                {
                    UpdateControlsValuesOnOptionChange(null, value, optionId);
                }
                OnOptionChanged(optionId);
            }
        }
        public static void SetOptionValueIfNotSaved(object optionId, object value, bool save = false, bool updateControls = false)
        {
             string key = GetKey(optionId);
             if (SavedOptions.Contains(key))
                 return;
             SetOptionValue(optionId, value, save, updateControls);
        }
        public static bool SaveOptions()
        {
            return Grabber.SaveOptions();
        }
        public static void GetOptions()
        {
            _SavedOptions = Grabber.ReadOptions();
        }

        public static string GetKey(object optionId)
        {
            string key = "";
            if (optionId != null && optionId.GetType().IsEnum)
            {
                key = optionId.GetType().FullName;
                key = key.Replace("+", ".").XReplace("_Group\\.", ".") + ".";
            }
            key += optionId.AsString();
            return key;
        }

        public static Option GetOption(object optionId)
        {
            string key = GetKey(optionId);
            if (RegisteredOptions.Contains(key))
            {
                return RegisteredOptions[key].Value as Option;
            }
            return null;
        }
        public static void RegisterGroups(OptionGroupCollection groups, Type optionNames)
        {
            string key = optionNames.FullName + ".";
            foreach (INamedGroup g in groups)
            {
                RegisterGroups(g, key + g.Name);
            }
        }
        
        private static void RegisterGroups(INamedGroup group, string key)
        {
            RegisterItems(group.Items, key);
            foreach (OptionGroup g in group.Groups)
            {
                RegisterGroups(g, key + "." + g.Name);
            }
        }
        private static void RegisterItems(IList items, string key)
        {
            foreach (INamedObject o in items)
                RegisteredOptions.Add(key + "." + o.Name, o);
        }

        #region dictionaries
        private static Dictionary<string, object> ChangedOptions = new Dictionary<string, object>();
        private static Dictionary<object, List<object>> EditsWithOption = new Dictionary<object, List<object>>();
        private static Dictionary<object, object> OptionsWithEdit = new Dictionary<object, object>();
        private static List<object> AutoSaveEdits = new List<object>();
        private static List<object> LockedObjects = new List<object>();
        private static Dictionary<BaseCheckedListBoxControl, string> Separators = new Dictionary<BaseCheckedListBoxControl, string>();
        private static Dictionary<BaseCheckedListBoxControl, Dictionary<object, object>> CheckedListBoxItemsOptions = new Dictionary<BaseCheckedListBoxControl, Dictionary<object, object>>();
        #endregion

        #region Lock/Unlock
        public static void LockObject(object obj)
        {
            LockedObjects.AddIfNotExist(obj);
        }
        public static void UnLockObject(object obj)
        {
            LockedObjects.Remove(obj);
        }
        #endregion

        #region Preparing objects
        private static void UnSetEvents(object obj)
        {
            if (obj is Component)
                (obj as Component).Disposed -= OptionCache_Disposed;

            if (obj is BaseEdit)
                (obj as BaseEdit).EditValueChanged -= OptionCache_EditValueChanged;

            if (obj is BarCheckItem)
                (obj as BarCheckItem).CheckedChanged -= OptionCache_CheckedChanged;

            if (obj is CheckedListBoxControl)
                (obj as CheckedListBoxControl).ItemCheck -= OptionCache_ItemCheck;
        }
        private static void SetEvents(object obj)
        {
            if (obj is Component)
                (obj as Component).Disposed += new EventHandler(OptionCache_Disposed);

            if (obj is BaseEdit)
                (obj as BaseEdit).EditValueChanged += new EventHandler(OptionCache_EditValueChanged);

            if (obj is BarCheckItem)
                (obj as BarCheckItem).CheckedChanged += new ItemClickEventHandler(OptionCache_CheckedChanged);

            if (obj is CheckedListBoxControl)
                (obj as CheckedListBoxControl).ItemCheck += new DevExpress.XtraEditors.Controls.ItemCheckEventHandler(OptionCache_ItemCheck);
        }
        private static void RemoveAll(object obj)
        {
            if (AutoSaveEdits.Contains(obj))
                AutoSaveEdits.Remove(obj);
            if (LockedObjects.Contains(obj))
                LockedObjects.Remove(obj);
            if (OptionsWithEdit.ContainsKey(obj))
            {
                object prevOption = OptionsWithEdit[obj];
                if (prevOption.IsNotNull())
                {
                    if (EditsWithOption.ContainsKey(prevOption))
                    {
                        List<object> list = EditsWithOption[prevOption];
                        if (list.Contains(obj))
                            list.Remove(obj);
                    }
                }
                OptionsWithEdit.Remove(obj);
            }
            if (obj is CheckedListBoxControl)
            {
                CheckedListBoxControl c = obj as CheckedListBoxControl;
                if (Separators.ContainsKey(c))
                    Separators.Remove(c);
                if (CheckedListBoxItemsOptions.ContainsKey(c))
                {
                    Dictionary<object, object> dic = CheckedListBoxItemsOptions[c];
                    foreach (KeyValuePair<object, object> kvp in dic)
                    {
                        if (EditsWithOption.ContainsKey(kvp.Value))
                        {
                            List<object> list = EditsWithOption[kvp.Value];
                            if (list.Contains(obj))
                                list.Remove(obj);
                        }
                    }
                    CheckedListBoxItemsOptions.Remove(c);
                }
            }
        }
        private static void AddControl(object obj, object optionId, bool autoSave)
        {
            List<object> optionEdits = null;
            string option = GetKey(optionId);
            OptionsWithEdit.Add(obj, option);
            if (autoSave)
                AutoSaveEdits.Add(obj);
            if (EditsWithOption.ContainsKey(option))
                optionEdits = EditsWithOption[option];
            else
            {
                optionEdits = new List<object>();
                EditsWithOption.Add(option, optionEdits);
            }
            optionEdits.AddIfNotExist(obj);
        }
        public static void PrepareObject(object obj, object optionId, bool autoSave, object defaultValue = null)
        {
            UnSetEvents(obj);
            RemoveAll(obj);
            string option = GetKey(optionId);
            if (option.IsNotNull())
            {
                AddControl(obj, option, autoSave);
                SetControlValueFromOption(obj, true, defaultValue);
                SetEvents(obj);
            }
        }

        public static void PrepareCheckedListBoxControl(CheckedListBoxControl control, string separator, object optionId, bool autoSave, object defVal = null)
        {
            UnSetEvents(control);
            RemoveAll(control);
            string option = GetKey(optionId); 
            if (option.IsNotNull())
            {
                Separators.Add(control, separator);
                AddControl(control, option, autoSave);
                SetControlValueFromOption(control, true, defVal);
                SetEvents(control);
            }
        }

        public static void PrepareCheckedListBoxControl(CheckedListBoxControl control, CheckedListBoxItemOption[] itemsOptions, bool autoSave)
        {
            UnSetEvents(control);
            RemoveAll(control);

            Dictionary<object, object> dic = new Dictionary<object, object>();

            List<object> list;
            if (itemsOptions != null && itemsOptions.Length > 0)
            {
                foreach (CheckedListBoxItemOption o in itemsOptions)
                {
                    if (o.ItemValue != null && !dic.ContainsKey(o.ItemValue) && o.Option.IsNotNull())
                    {
                        dic.Add(o.ItemValue, o.Option);
                        if (EditsWithOption.ContainsKey(o.Option))
                            list = EditsWithOption[o.Option];
                        else
                        {
                            list = new List<object>();
                            EditsWithOption.Add(o.Option, list);
                        }
                        if (!list.Contains(control))
                            list.Add(control);
                    }
                }
            }
            if (dic.Count > 0)
            {
                CheckedListBoxItemsOptions.Add(control, dic);
                SetControlValueFromOption(control, true);
                SetEvents(control);
            }
        }
        #endregion

        #region setting controls values base on option(s)
        public static void SetControlValueFromOption(object control, bool lockChangingOthers, object defVal = null)
        {
            if (OptionsWithEdit.ContainsKey(control))
            {
                object option = OptionsWithEdit[control];
                if (option.IsNotNull())
                {
                    SetControlValueFromOption(control, option, GetOptionValue(option, defVal), lockChangingOthers);
                }
            }
            else if (control is CheckedListBoxControl)
            {
                CheckedListBoxControl c = control as CheckedListBoxControl;
                if (CheckedListBoxItemsOptions.ContainsKey(c))
                {
                    if (CheckedListBoxItemsOptions.ContainsKey(c))
                    {
                        Dictionary<object, object> dic  = CheckedListBoxItemsOptions[c];
                        bool val;
                        foreach (CheckedListBoxItem item in c.Items)
                        {
                            if (item.Value != null && dic.ContainsKey(item.Value))
                            {
                                val = GetOptionBoolValue(dic[item.Value]);
                                item.CheckState = (val ? CheckState.Checked : CheckState.Unchecked);
                                SetControlValueFromOption(control, dic[item.Value], val, lockChangingOthers);
                            }
                        }
                    }
                }
            }
        }
        private static void SetControlValueFromOption(object control, object optionId, object optionValue, bool lockChangingOthers)
        {
            if (control is ColorEdit)
                (control as ColorEdit).Color = optionValue.AsColor();
            else if (control is BaseEdit)
                (control as BaseEdit).EditValue = optionValue;
            else if (control is BarCheckItem)
                (control as BarCheckItem).Checked = optionValue.AsBool();
            else if (control is CheckedListBoxControl)
            {
                CheckedListBoxControl c = control as CheckedListBoxControl;
                if (Separators.ContainsKey(c))
                    SetCheckedItems(c, optionValue.AsString());
                else if (CheckedListBoxItemsOptions.ContainsKey(c))
                {
                    Dictionary<object, object> dic = CheckedListBoxItemsOptions[c];
                    foreach (CheckedListBoxItem item in c.Items)
                    {
                        if (item.Value != null && dic.ContainsKey(item.Value) && dic[item.Value] == optionId)
                        {
                            item.CheckState = (optionValue.AsBool() ? CheckState.Checked : CheckState.Unchecked);
                        }
                    }
                }
            }
        }
        private static void SetCheckedItems(CheckedListBoxControl control, string str)
        {
            if (Separators.ContainsKey(control))
            {
                string[] args = str.Split(new string[] { Separators[control] }, StringSplitOptions.RemoveEmptyEntries);
                foreach (CheckedListBoxItem item in control.Items)
                {
                    if (args.Contains(item.Value.AsString()))
                        item.CheckState = CheckState.Checked;
                    else
                        item.CheckState = CheckState.Unchecked;
                }
            }
        }

        #endregion

        #region setting options values from controls
        public static void SetOptionFromControl(object control, bool save)
        {
            if (control != null && !LockedObjects.Contains(control))
            {
                if (OptionsWithEdit.ContainsKey(control))
                {
                    object optionId = OptionsWithEdit[control];
                    object val = null;
                    if (control is BaseEdit)
                        val = (control as BaseEdit).EditValue;
                    else if (control is BarCheckItem)
                        val = (control as BarCheckItem).Checked;
                    else if (control is CheckedListBoxControl)
                        val = GetCheckedItemsStr(control as CheckedListBoxControl);

                    SetOptionValue(optionId, val, save, true);
                }
                else if (control is CheckedListBoxControl)
                {
                    CheckedListBoxControl c = control as CheckedListBoxControl;
                    if (CheckedListBoxItemsOptions.ContainsKey(c))
                    {
                        Dictionary<object, object> dic = CheckedListBoxItemsOptions[c];
                        foreach (CheckedListBoxItem item in c.Items)
                        {
                            if (item.Value.IsNotNull() && dic.ContainsKey(item.Value))
                            {
                                SetOptionValue(dic[item.Value], item.CheckState == CheckState.Checked ? true : false, save, true);
                            }
                        }
                    }
                }
            }
        }
        public static void SetOptionFromControl(object control)
        {
            SetOptionFromControl(control, AutoSaveEdits.Contains(control));
        }
        private static string GetCheckedItemsStr(CheckedListBoxControl c)
        {
            string str = "";
            if (Separators.ContainsKey(c))
            {
                string separator = Separators[c];
                foreach (int i in c.CheckedIndices)
                {
                    str = str.AppendString(separator);
                    str += c.Items[i].Value.AsString();
                }
            }
            return str;
        }

        #endregion

        public static void UpdateControlsValuesOnOptionChange(object sender, object value, object optionId = null)
        {
            if (temporaryLock)
                return;

            if (sender != null && LockedObjects.Contains(sender))
                return;

            temporaryLock = true;

            if (optionId.IsNull() && sender != null && OptionsWithEdit.ContainsKey(sender))
                optionId = OptionsWithEdit[sender];
            if (optionId.IsNull())
                return;

            if (EditsWithOption.ContainsKey(optionId))
            {
                foreach (object o in EditsWithOption[optionId])
                {
                    if (o != sender)
                        SetControlValueFromOption(o, optionId, value, true);
                }
            }

            temporaryLock = false;
        }

        #region Controls  events
        private static void OptionCache_Disposed(object sender, EventArgs e)
        {
            try
            {
                UnSetEvents(sender);
                RemoveAll(sender);
            }
            catch { }
        }

        private static void OptionCache_CheckedChanged(object sender, ItemClickEventArgs e)
        {
            SetOptionFromControl(sender);
        }

        private static void OptionCache_ItemCheck(object sender, DevExpress.XtraEditors.Controls.ItemCheckEventArgs e)
        {
            SetOptionFromControl(sender);
        }

        private static void OptionCache_EditValueChanged(object sender, EventArgs e)
        {
            SetOptionFromControl(sender);
        }

        #endregion
        #endregion
    }

    public delegate void OptionChangedEventHandler(object optionId);
}
